package cn.gs.exchanger.agent;

// @author gs
import cn.gs.exchanger.ExchangerClient;
import cn.gs.exchanger.ExchangerClient.IService;
import cn.gs.exchanger.ExchangerException;
import cn.gs.serializable.entity.EntityController;
import cn.gs.serializable.serialize.ClassSerialize;
import cn.gs.serializable.serialize.SerializeController;
import cn.gs.util.monitor.ITimerMonitorTask;
import cn.gs.util.monitor.TimerMonitor;
import cn.gs.util.set.Pair;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

public class AgentServer {

    private void tryPut(String key, Set<String> set) {
        while (true) {
            Set<String> r = assigned.putIfAbsent(key, set);
            if (r != null) {
                r.addAll(set);
            }
            if (assigned.get(key) == r) {
                return;
            }
        }
    }

    private void tryPut(String key, String... set) {
        tryPut(key, new HashSet<String>(Arrays.asList(set)));
    }

    public <T> T toObj(String server, String id, Class<T> clazz) {
        if (serverId.equals(server)) {
            return (T) objs.get(id);
        } else {
            try {
                return client.noProxy(server, id, clazz);
            } catch (ExchangerException ex) {
                Logger.getLogger(AgentServer.class.getName()).log(Level.SEVERE, null, ex);
            }
            return null;
        }
    }

    public String fromObj(String client, Object o) {
        String id = String.valueOf(o == null ? 0 : 79 + o.hashCode() * 17 + o.getClass().hashCode() * 53);
        objs.put(id, o);
        tryPut(client, id);
        return id;
    }
    final String serverId = UUID.randomUUID().toString();
    TimerMonitor monitor;
    AgentClient client;
    ExchangerClient exchanger;
    private Map<String, Object> objs = new ConcurrentHashMap<String, Object>();
    private ConcurrentHashMap<String, Set<String>> assigned = new ConcurrentHashMap<String, Set<String>>();
    Set<Class> cls = new HashSet<Class>();
    ClassSerialize clzs = new ClassSerialize();
    ServerAgentSerialize serverAgent;
    SerializeController sc;

    public AgentServer(ExchangerClient exchanger, TimerMonitor monitor) throws ExchangerException {
        this.exchanger = exchanger;
        this.client = new AgentClient(exchanger);
        serverAgent = new ServerAgentSerialize(this);
        sc = new SerializeController(serverAgent);
        exchanger.register(CAgent.DELETE + "@" + serverId, new RemoveCommand(), true);
        exchanger.register(CAgent.NO_PROXY + "@" + serverId, new NoProxyCommand(), true);
        this.monitor = monitor;
        monitor.add(new MonitorTask(), CAgent.SERVER_CHECK_CYCLE, 0);
    }

    public final boolean add(Class... cls) {
        try {
            for (Class c : cls) {
                for (Method m : c.getMethods()) {
                    if (!Object.class.equals(m.getDeclaringClass())) {
                        exchanger.register(CAgent.ASK + m.getDeclaringClass().getCanonicalName() + "." + m.getName() + "@" + serverId, new AskCommand(m), false);
                    }
                }
                exchanger.register(CAgent.CREATE + c.getCanonicalName(), new CreateCommand(c), false);
            }
        } catch (ExchangerException ex) {
            Logger.getLogger(AgentServer.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        this.cls.addAll(Arrays.asList(cls));
        client.add(cls);
        return true;
    }

    private class AskCommand implements IService {

        Method m;

        AskCommand(Method m) {
            this.m = m;
        }

        /**
         * @param args List(clientId, id, args...)
         * @return List(return)
         */
        @Override
        public List<String> process(List<String> args) throws ExchangerException {
            if (args != null && args.size() >= 2) {
                String client = args.get(0), id = args.get(1);
                Object obj = objs.get(id);
                if (obj == null) {
                    throw ExchangerException.Type.ARGS_ERR.create("Object_not_exist", null);
                }
                //Convert args to Object
                Object[] os = new Object[args.size() - 2];
                Class[] ot = m.getParameterTypes();
                EntityController ec = new EntityController();
                for (int i = 2; i < args.size(); ++i) {
                    Object o = sc.toObject(args.get(i), ot[i - 2]);
                    os[i - 2] = o;
                }
                //Invoke
                Object ret;
                try {
                    ret = m.invoke(obj, os);
                } catch (Throwable ex) {
                    Logger.getLogger(AgentServer.class.getName()).log(Level.SEVERE, null, ex);
                    throw ExchangerException.Type.EXECUTE_ERR.create("Invoke_error", ex);
                }
                //Convert return to String
                serverAgent.clientId.set(client);
                String s = sc.toString(ret);
                serverAgent.clientId.remove();
                return Arrays.asList(s);
            }
            throw ExchangerException.Type.ARGS_ERR.create("Empty_Args", null);
        }
    }

    private class CreateCommand implements IService {

        Class clazz;

        CreateCommand(Class clazz) {
            this.clazz = clazz;
        }

        /**
         * @param args List(clientId)
         * @return List(id, server)
         */
        @Override
        public List<String> process(List<String> args) throws ExchangerException {
            if (args != null && args.size() > 0 && !args.get(0).isEmpty()) {
                String clientId = args.get(0);
                try {
                    Object o = clazz.newInstance();
                    if (o != null) {
                        String id = UUID.randomUUID().toString();
                        tryPut(clientId, id);
                        objs.put(id, o);
                        return Arrays.asList(id, serverId);
                    }
                } catch (Throwable ex) {
                    Logger.getLogger(AgentServer.class.getName()).log(Level.SEVERE, null, ex);
                    throw ExchangerException.Type.ARGS_ERR.create("Class_has_not_no_args_constructor.", ex);
                }
            }
            throw ExchangerException.Type.ARGS_ERR.create("Empty_Args", null);
        }
    }

    private class RemoveCommand implements IService {

        /**
         * @param args List(id...)
         * @return List(True)
         */
        @Override
        public List<String> process(List<String> args) throws ExchangerException {
            if (args != null) {
                for (String id : args) {
                    objs.remove(id);
                }
                return Arrays.asList("true");
            }
            throw ExchangerException.Type.ARGS_ERR.create("Empty_Args", null);
        }
    }

    private class NoProxyCommand implements IService {

        /**
         * @param args List(id)
         * @return List(Object, Class)
         */
        @Override
        public List<String> process(List<String> args) throws ExchangerException {
            if (args != null && !args.isEmpty()) {
                String id = args.get(0);
                Object o = objs.get(id);
                if (o != null) {
                    String clz = clzs.forString(o.getClass());
                    String ser = SerializeController.staticToString(o);
                    return Arrays.asList(ser, clz);
                }
            }
            throw ExchangerException.Type.ARGS_ERR.create("Args_Error", null);
        }
    }

    private class MonitorTask implements ITimerMonitorTask {

        @Override
        public void start() {
            //Nothing to do
        }

        @Override
        public boolean retry() {
            //Not support;
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public void fail() {
            monitor.add(this, CAgent.SERVER_CHECK_CYCLE, 0);
            Set<String> clients = assigned.keySet();
            Set<String> objIds = objs.keySet();
            for (String c : clients) {
                Pair<List<String>, ExchangerException> a = exchanger.ask(CAgent.LIVE + "@" + c);
                if (a.getSecond() != null) {
                    Logger.getLogger(MonitorTask.class.getName()).log(Level.INFO, "AgentClient:" + c + "_Lost_Connect", a.getSecond());
                    Set<String> ids = assigned.remove(c);
                    for (String id : ids) {
                        objs.remove(id);
                    }
                } else {
                    //Check Ids
                    assigned.get(c).retainAll(objIds);
                }
            }
        }
    }
}
